// File handling routines
//
// Possible performance improvement: make a subclass or alternate class
// that reads an entire file into memory and fulfills read/write requests
// in memory (or equivalently, use a memory mapped file). This can greatly
// improve performance if there are files we do a lot of read/write requests on.
//
// We generally are dealing with files that can be very large and want to
// minimize memory usage, so this is not high priority
//
// this has the beginnings of asynchronous access support
//
// Unfortunately, doing asynchronous reads with FILE_FLAG_OVERLAPPED buys us
// nothing because the system cache manager enforces serial requests.
//
// Hence, we need to also use FILE_FLAG_NO_BUFFERING. this has a number of
// constraints:
// - file read/write position must be aligned on multiples of the disk sector
//   size
// - file read/write length must be a multiple of the sector size
// - read/write buffer must be aligned on multiples of the disk sector size
//
// In particular, this means that we cannot write 8 bytes, for example, because
// we have to write an entire sector.
//
// Currently, the implementation only supports enough to do some simple read
// tests
//
// The general idea is code that wants to to a sequence of asynchronous actions
// will look like the following, for an example of reading multiple event
// records asynchronously:
//
// uint32 async_id = File::GetNextAsyncId()
// while (!done) {
//   for (everything_to_do, e.g., for each event to read) {
//     call File::Read to read items needed;
//     returns TR_E_FILE_ASYNC_PENDING if queued; or returns data if done
//     process the item (e.g., event) if desired
//   }
//   call some routine to process pending completions; initiate delayed action
// }
// call some cleanup routine

#define NOMINMAX
#include "file.h"
#include "debug.h"
#include "error.h"
#include "logging.h"
#include "path.h"
#include "string.h"
#include "utils.h"
#include "app_util.h"
#include "scoped_ptr_address.h"
#include "reg_key.h"

// Constants
const uint32 kZeroSize = 4096;  // Buffer size used for clearing data in a file.

// The moves-pending-reboot is a MULTISZ registry key in the HKLM part of the
// registry.
static const TCHAR* kSessionManagerKey =
_T("HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager");
static const TCHAR* kPendingFileRenameOps = _T("PendingFileRenameOperations");

File::File() : handle_(INVALID_HANDLE_VALUE), read_only_(false), sequence_id_(0)
{
}

File::~File()
{
	if (handle_ != INVALID_HANDLE_VALUE)
		VERIFY1(SUCCEEDED(Close()));
}

// open for reading only if write == false, otherwise both reading and writing
// allow asynchronous operations if async == true. Use this function when you
// need exclusive access to the file.
HRESULT File::Open(const TCHAR* file_name, bool write, bool async)
{
	return OpenShareMode(file_name, write, async, 0);
}

// Allows specifying a sharing mode such as FILE_SHARE_READ. Otherwise,
// this is identical to File::Open().
HRESULT File::OpenShareMode(const TCHAR* file_name,
							bool write,
							bool async,
							DWORD share_mode)
{
	ASSERT1(file_name && *file_name);
	ASSERT1(handle_ == INVALID_HANDLE_VALUE);
	VERIFY1(!async);

	file_name_ = file_name;

	// there are restrictions on what we can do if using FILE_FLAG_NO_BUFFERING
	// if (!buffer) { flags |= FILE_FLAG_NO_BUFFERING; }
	// FILE_FLAG_WRITE_THROUGH
	// how efficient is NTFS encryption? FILE_ATTRIBUTE_ENCRYPTED
	// FILE_ATTRIBUTE_TEMPORARY
	// FILE_FLAG_RANDOM_ACCESS
	// FILE_FLAG_SEQUENTIAL_SCAN

	handle_ = ::CreateFile(file_name,
		write ? (FILE_WRITE_DATA       |
		FILE_WRITE_ATTRIBUTES |
		FILE_READ_DATA) : FILE_READ_DATA,
		share_mode,
		NULL,
		write ? OPEN_ALWAYS : OPEN_EXISTING,
		FILE_FLAG_RANDOM_ACCESS,
		NULL);

	if (handle_ == INVALID_HANDLE_VALUE) {
		HRESULT hr = HRESULTFromLastError();
		UTIL_LOG(LEVEL_ERROR,
			(_T("[File::OpenShareMode - CreateFile failed][%s][%d][%d][0x%x]"),
			file_name, write, async, hr));
		return hr;
	}

	// This attribute is not supported directly by the CreateFile function.
	if (write &&
		!::SetFileAttributes(file_name, FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)) {
			HRESULT hr = HRESULTFromLastError();
			UTIL_LOG(LEVEL_ERROR,
				(_T("[File::OpenShareMode - SetFileAttributes failed][0x%x]"), hr));
			return hr;
	}

	read_only_ = !write;
	pos_ = 0;
	return S_OK;
}

// The path must not be enclosed in quotes. This is the Windows standard.
// ::GetFileAttributesEx() returns ERROR_INVALID_NAME for quoted paths.
bool File::Exists(const TCHAR* file_name) {
	ASSERT1(file_name && *file_name);
	ASSERT1(lstrlen(file_name) > 0);

	// NOTE: This is the fastest implementation I found.  The results were:
	//   CreateFile           1783739 avg ticks/call
	//   FindFirstFile         634148 avg ticks/call
	//   GetFileAttributes     428714 avg ticks/call
	//   GetFileAttributesEx   396324 avg ticks/call
	WIN32_FILE_ATTRIBUTE_DATA attrs = {0};
	return 0 != ::GetFileAttributesEx(file_name, ::GetFileExInfoStandard, &attrs);
}

bool File::IsDirectory(const TCHAR* file_name) {
	ASSERT1(file_name && *file_name);

	WIN32_FILE_ATTRIBUTE_DATA attrs;
	SetZero(attrs);
	if (!::GetFileAttributesEx(file_name, ::GetFileExInfoStandard, &attrs)) {
		UTIL_LOG(LEVEL_ERROR,
			(_T("[File::IsDirectory - GetFileAttributesEx failed][%s][0x%x]"),
			file_name, HRESULTFromLastError()));
		return false;
	}

	return (attrs.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
}

HRESULT File::GetWildcards(const TCHAR* dir,
						   const TCHAR* wildcard,
						   std::vector<CString>* matching_paths) {
							   ASSERT1(dir && *dir);
							   ASSERT1(wildcard && *wildcard);
							   ASSERT1(matching_paths);

							   matching_paths->clear();

							   // Make sure directory name ends with "\"
							   CString directory = String_MakeEndWith(dir, _T("\\"), false);

							   WIN32_FIND_DATA find_data;
							   SetZero(find_data);
							   scoped_hfind hfind(::FindFirstFile(directory + wildcard, &find_data));
							   if (!hfind) {
								   HRESULT hr = HRESULTFromLastError();
								   UTIL_LOG(L5, (_T("[File::GetWildcards - FindFirstFile failed][0x%x]"), hr));
								   return hr;
							   }
							   do {
								   if (find_data.dwFileAttributes == FILE_ATTRIBUTE_NORMAL ||
									   !(find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
										   CString to_file(directory + find_data.cFileName);
										   matching_paths->push_back(to_file);
								   }
							   } while (::FindNextFile(get(hfind), &find_data));

							   HRESULT hr = HRESULTFromLastError();
							   if (hr != HRESULT_FROM_WIN32(ERROR_NO_MORE_FILES)) {
								   UTIL_LOG(LEVEL_ERROR,
									   (_T("[File::GetWildcards - FindNextFile failed][0x%x]"), hr));
								   return hr;
							   }
							   return S_OK;
}

// returns error if cannot remove
// returns success if removed or already removed
HRESULT File::Remove(const TCHAR* file_name) {
	ASSERT1(file_name && *file_name);

	if (!Exists(file_name)) {
		return S_OK;
	}

	if (!::DeleteFile(file_name)) {
		return HRESULTFromLastError();
	}

	return S_OK;
}

HRESULT File::CopyWildcards(const TCHAR* from_dir,
							const TCHAR* to_dir,
							const TCHAR* wildcard,
							bool replace_existing_files) {
								ASSERT1(from_dir && *from_dir);
								ASSERT1(to_dir && *to_dir);
								ASSERT1(wildcard && *wildcard);

								// Make sure dir names end with a "\"
								CString from_directory = String_MakeEndWith(from_dir, _T("\\"), false);
								CString to_directory = String_MakeEndWith(to_dir, _T("\\"), false);

								// Get full path to source files (which is a wildcard)
								CString from_files(from_directory + wildcard);

								// Run over all files that match wildcard
								WIN32_FIND_DATA find_data;
								SetZero(find_data);

								scoped_hfind hfind(::FindFirstFile(from_files, &find_data));
								if (!hfind) {
									HRESULT hr = HRESULTFromLastError();
									UTIL_LOG(LEVEL_ERROR,
										(_T("[File::CopyWildcards - FindFirstFile failed][0x%x]"), hr));
									return hr;
								}
								do {
									// Copy files
									if (find_data.dwFileAttributes == FILE_ATTRIBUTE_NORMAL ||
										!(find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
											CString from_file(from_directory + find_data.cFileName);
											CString to_file(to_directory + find_data.cFileName);

											if (!replace_existing_files && Exists(to_file)) {
												// Continue, since the caller has explicitly asked us to not replace an
												// existing file
												continue;
											}

											RET_IF_FAILED(Copy(from_file, to_file, replace_existing_files));
									}
								} while (::FindNextFile(get(hfind), &find_data));

								HRESULT hr = HRESULTFromLastError();
								if (hr != HRESULT_FROM_WIN32(ERROR_NO_MORE_FILES)) {
									UTIL_LOG(LEVEL_ERROR,
										(_T("[File::CopyWildcards - FindNextFile failed][0x%x]"), hr));
									return hr;
								}
								return S_OK;
}

HRESULT File::CopyTree(const TCHAR* from_dir,
					   const TCHAR* to_dir,
					   bool replace_existing_files) {
						   ASSERT1(from_dir && *from_dir);
						   ASSERT1(to_dir && *to_dir);

						   UTIL_LOG(L3, (L"[File::CopyTree][from_dir %s][to_dir %s][replace %d]",
							   from_dir, to_dir, replace_existing_files));

						   // Make sure dir names end with a "\"
						   CString from_directory(String_MakeEndWith(from_dir, L"\\", false));
						   CString to_directory(String_MakeEndWith(to_dir, L"\\", false));

						   RET_IF_FAILED(CreateDir(to_directory, NULL));
						   RET_IF_FAILED(CopyWildcards(from_directory,
							   to_directory,
							   L"*.*",
							   replace_existing_files));

						   // Run over all directories
						   WIN32_FIND_DATA find_data;
						   SetZero(find_data);

						   CString from_files(from_directory);
						   from_files += _T("*.*");

						   scoped_hfind hfind(::FindFirstFile(from_files, &find_data));
						   if (!hfind) {
							   HRESULT hr = HRESULTFromLastError();
							   UTIL_LOG(LEVEL_ERROR,
								   (_T("[File::CopyTree - FindFirstFile failed][0x%x]"), hr));
							   return hr;
						   }
						   do {
							   // Copy files
							   if ((find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0 &&
								   String_StrNCmp(find_data.cFileName, L"..", 2, false) &&
								   String_StrNCmp(find_data.cFileName, L".", 2, false)) {
									   CString from_subdir(from_directory + find_data.cFileName);
									   CString to_subdir(to_directory + find_data.cFileName);
									   RET_IF_FAILED(CopyTree(from_subdir, to_subdir, replace_existing_files));
							   }
						   } while (::FindNextFile(get(hfind), &find_data));

						   HRESULT hr = HRESULTFromLastError();
						   if (hr != HRESULT_FROM_WIN32(ERROR_NO_MORE_FILES)) {
							   UTIL_LOG(LEVEL_ERROR,
								   (_T("[File::CopyTree - FindNextFile failed][0x%x]"), hr));
							   return hr;
						   }

						   return S_OK;
}

HRESULT File::Copy(const TCHAR* from,
				   const TCHAR* to,
				   bool replace_existing_file) {
					   ASSERT1(from && *from);
					   ASSERT1(to && *to);

					   if (!replace_existing_file && Exists(to)) {
						   // Return success, since the caller has explicitly asked us to not replace
						   // an existing file
						   return S_OK;
					   }

					   if (!::CopyFile(from, to, !replace_existing_file)) {
						   HRESULT hr = HRESULTFromLastError();
						   UTIL_LOG(LEVEL_ERROR, (_T("[File::Copy - CopyFile failed]")
							   _T("[from=%s][to=%s][replace=%u][0x%x]"),
							   from, to, replace_existing_file, hr));
						   return hr;
					   }

					   return S_OK;
}

// TODO: Combine common code in Move/MoveAfterReboot
HRESULT File::Move(const TCHAR* from,
				   const TCHAR* to,
				   bool replace_existing_file) {
					   ASSERT1(from && *from);
					   ASSERT1(to && *to);

					   DWORD flags = MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH;
					   if (replace_existing_file) {
						   flags |= MOVEFILE_REPLACE_EXISTING;
					   }

					   if (!::MoveFileEx(from, to, flags)) {
						   HRESULT hr = HRESULTFromLastError();
						   UTIL_LOG(LEVEL_ERROR,
							   (_T("[File::Move - MoveFileEx failed]")
							   _T("[from=%s][to=%s][replace=%u][0x%x]"),
							   from, to, replace_existing_file, hr));
						   return hr;
					   }

					   return S_OK;
}

// DeleteAfterReboot tries to delete the files by either moving them to the TEMP
// directory and deleting them on reboot, or if that fails, by trying to delete
// them in-place on reboot
HRESULT File::DeleteAfterReboot(const TCHAR* from) {
	ASSERT1(from && *from);

	if (File::Exists(from)) {
		HRESULT hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
		CString from_temp;

		// No point in moving into TEMP if we're already there
		if (!String_StartsWith(from, app_util::GetTempDir(), true)) {
			// Try to move to the TEMP directory first
			CString temp_dir(String_MakeEndWith(app_util::GetTempDir(),
				_T("\\"),
				false));
			// Of the form "C:\\Windows\\Temp\\FROM.EXE1f4c0b7f"
			from_temp.Format(_T("%s%s%x"),
				temp_dir,
				GetFileFromPath(from),
				::GetTickCount());

			hr = File::Move(from, from_temp, true);
			UTIL_LOG(L2, (_T("[File::DeleteAfterReboot - move %s to %s][0x%x]"),
				from, from_temp, hr));
		}

		if (SUCCEEDED(hr)) {
			UTIL_LOG(L2, (_T("[File::DeleteAfterReboot - delete %s after reboot]"),
				from_temp));
			// Move temp file after reboot
			if (FAILED(hr = File::MoveAfterReboot(from_temp, NULL))) {
				UTIL_LOG(LEVEL_ERROR, (_T("[DeleteWildcardFiles]")
					_T("[failed to delete after reboot %s][0x%x]"),
					from_temp, hr));
			}
		} else  {
			// Move original file after reboot
			if (FAILED(hr = File::MoveAfterReboot(from, NULL))) {
				UTIL_LOG(LEVEL_ERROR, (_T("[DeleteWildcardFiles]")
					_T("[failed to delete after reboot %s][0x%x]"),
					from, hr));
			}
		}

		return hr;
	}

	return S_OK;
}


HRESULT File::MoveAfterReboot(const TCHAR* from, const TCHAR* to) {
	ASSERT1(from && *from);

	if (!File::Exists(from)) {
		// File/directory doesn't exist, should this return failure or success?
		// Decision:  Failure.  Because the caller can decide if it is really
		// failure or not in his specific case.
		UTIL_LOG(LEVEL_WARNING, (_T("[File::MoveAfterReboot]")
			_T("[file doesn't exist][from %s]"), from));
		return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
	}

	DWORD flags = MOVEFILE_DELAY_UNTIL_REBOOT;
	if (!File::IsDirectory(from)) {
		// This flag valid only for files
		flags |= MOVEFILE_REPLACE_EXISTING;
	}

	if (!::MoveFileEx(from, to, flags)) {
		HRESULT hr = HRESULTFromLastError();
		UTIL_LOG(LEVEL_ERROR, (_T("[File::MoveAfterReboot]")
			_T("[failed to MoveFileEx from '%s' to '%s'][0x%x]"),
			from, to, hr));
		return hr;
	}

	return S_OK;
}

// See if we have any moves pending a reboot. Return SUCCESS if we do
// not encounter errors (not finding a move is not an error). We need to
// also check the value of *found_ptr for whether we actually found a move.
// On return, *value_multisz_ptr is the value within
// "PendingFileRenameOperations", but with any moves for in_directory removed
// from it.
// The prefix_match boolean controls whether we do an exact match on
// in_directory, or remove all entries with the in_directory prefix.
// NOTE: If the only values found were our own keys, the whole
// PendingFileRenameOperations MULTISZ needs to be deleted.
// This is signified by a returned *value_size_chars_ptr of 0.
HRESULT File::GetPendingRenamesValueMinusDir(const TCHAR* in_directory,
											 bool prefix_match,
											 TCHAR** value_multisz_ptr,
											 DWORD* value_size_chars_ptr,
											 bool* found_ptr) {
												 ASSERT1(in_directory && *in_directory);

												 // Convert to references for easier-to-read-code:
												 TCHAR*& value_multisz = *value_multisz_ptr;
												 DWORD& value_size_chars = *value_size_chars_ptr;
												 bool& found = *found_ptr;

												 // Initialize [out] parameters
												 value_multisz = NULL;
												 value_size_chars = 0;
												 found = false;

												 // Locals mirroring the [out] parameters.
												 // We will only set the corresponding [out] parameters when we have something
												 // meaningful to return to the caller
												 scoped_array<TCHAR> value_multisz_local;
												 DWORD value_size_chars_local = 0;

												 DWORD value_size_bytes = 0;
												 // Get the current value of the key
												 // If the Key is missing, that's totally acceptable.
												 RET_IF_FALSE(
													 RegKey::HasValue(kSessionManagerKey, kPendingFileRenameOps) &&
													 SUCCEEDED(RegKey::GetValue(kSessionManagerKey,
													 kPendingFileRenameOps,
													 reinterpret_cast<byte**>(&value_multisz_local),
													 &value_size_bytes)),
													 S_OK);

												 ASSERT1(value_multisz_local.get() || value_size_bytes == 0);
												 UTIL_LOG(L5, (_T("[File::GetPendingRenamesValueMinusDir]")
													 _T("[read multisz %d bytes]"),
													 value_size_bytes));
												 RET_IF_FALSE(value_size_bytes > 0, S_OK);

												 // The size should always be aligned to a TCHAR boundary, otherwise the key
												 // is corrupted.
												 ASSERT1((value_size_bytes % sizeof(TCHAR)) == 0);
												 RET_IF_FALSE((value_size_bytes % sizeof(TCHAR)) == 0,
													 HRESULT_FROM_WIN32(ERROR_BADKEY));
												 // Valid size, so convert to TCHARs:
												 value_size_chars_local = value_size_bytes / sizeof(TCHAR);

												 // Buffer must terminate with two nulls
												 ASSERT(value_size_chars_local >= 2 &&
													 !value_multisz_local[value_size_chars_local - 1] &&
													 !value_multisz_local[value_size_chars_local - 2],
													 (_T("buffer must terminate with two nulls")));
												 RET_IF_FALSE(value_size_chars_local >= 2 &&
													 !value_multisz_local[value_size_chars_local - 1] &&
													 !value_multisz_local[value_size_chars_local - 2],
													 HRESULT_FROM_WIN32(ERROR_BADKEY));
												 // Mark the end of the string.
												 // multisz_end will point at the character past end of buffer:
												 TCHAR* multisz_end = value_multisz_local.get() + value_size_chars_local;

												 // We're looking for \??\C:\...  The \??\ was
												 // added by the OS to the directory name we specified.
												 CString from_dir(_T("\\??\\"));
												 from_dir += in_directory;
												 DWORD from_dir_len = from_dir.GetLength();

												 // A MULTISZ is a list of null terminated strings, terminated by a double
												 // null.  We keep two pointers marching along the string in parallel.
												 TCHAR* str_read = value_multisz_local.get();
												 TCHAR* str_write = str_read;

												 while ((str_read < multisz_end) && *str_read) {
													 size_t str_len = ::lstrlen(str_read);
													 // A FALSE here indicates a corrupt PendingFileRenameOperations
													 RET_IF_FALSE((str_read + str_len + 1) < multisz_end,
														 HRESULT_FROM_WIN32(ERROR_BADKEY));
													 if (0 == String_StrNCmp(str_read,
														 from_dir,
														 from_dir_len + (prefix_match ? 0 : 1),
														 true)) {
															 // String matches, we want to remove this string, so advance only the
															 // read pointer - past this string and the replacement string.
															 UTIL_LOG(L5, (_T("[File::GetPendingRenamesValueMinusDir]")
																 _T("[skips past match '%s']"),
																 str_read));
															 str_read += str_len + 1;
															 str_read += ::lstrlen(str_read) + 1;
															 continue;
													 }
													 // String doesn't match, we want to keep it.
													 if (str_read != str_write) {
														 // Here we're not in sync in the buffer, we've got to move two
														 // strings down.
														 UTIL_LOG(L5, (_T("[File::GetPendingRenamesValueMinusDir]")
															 _T("[copying some other deletion][%s][%s]"),
															 str_read, str_read + ::lstrlen(str_read) + 1));
														 ASSERT1(str_write < str_read);
														 String_StrNCpy(str_write, str_read, str_len+1);
														 str_read += str_len + 1;
														 str_write += str_len + 1;
														 str_len = ::lstrlen(str_read);
														 String_StrNCpy(str_write, str_read, str_len+1);
														 str_read += str_len + 1;
														 str_write += str_len + 1;
													 } else {
														 // We're in sync in the buffer, advance both pointers past two strings
														 UTIL_LOG(L5, (_T("[File::GetPendingRenamesValueMinusDir]")
															 _T("[skipping past some other deletion][%s][%s]"),
															 str_read, str_read + ::lstrlen(str_read) + 1));
														 str_read += str_len + 1;
														 str_read += ::lstrlen(str_read) + 1;
														 str_write = str_read;
													 }
												 }

												 // A FALSE here indicates a corrupt PendingFileRenameOperations
												 RET_IF_FALSE(str_read < multisz_end,
													 HRESULT_FROM_WIN32(ERROR_BADKEY));

												 if (str_read != str_write) {
													 // We found some values
													 found = true;

													 if (str_write == value_multisz_local.get()) {
														 // The only values were our own keys,
														 // and the whole PendingFileRenameOperations
														 // value needs to be deleted. We do not populate
														 // value_size_chars or value_multisz in this case.
														 ASSERT1(!value_size_chars);
														 ASSERT1(!value_multisz);
													 } else  {
														 // The last string should have a NULL terminator:
														 ASSERT1(str_write[-1] == '\0');
														 RET_IF_FALSE(str_write[-1] == '\0',
															 HRESULT_FROM_WIN32(ERROR_INVALID_DATA));
														 // a REG_MULTI_SZ needs to be terminated with an extra NULL.
														 *str_write = '\0';
														 ++str_write;

														 // Populate value_size_chars and value_multisz in this case.
														 value_multisz = value_multisz_local.release();
														 value_size_chars = str_write - value_multisz;
													 }
												 }

												 return S_OK;
}

// Remove any moves pending a reboot from the PendingFileRenameOperations
// in the registry.
// The prefix_match boolean controls whether we do an exact match on
// in_directory, or remove all entries with the in_directory prefix.
HRESULT File::RemoveFromMovesPendingReboot(const TCHAR* in_directory,
										   bool prefix_match)
{
	ASSERT1(in_directory && *in_directory);

	bool found = false;
	// scoped_array will free the value_multisz buffer on stack unwind:
	scoped_array<TCHAR> value_multisz;
	DWORD value_size_chars = 0;
	HRESULT hr = GetPendingRenamesValueMinusDir(in_directory,
		prefix_match,
		address(value_multisz),
		&value_size_chars,
		&found);
	if (SUCCEEDED(hr) && found) {
		if (value_multisz.get() == NULL)  {
			// There's no point in writing an empty value_multisz.
			// Let's delete the PendingFileRenameOperations value
			UTIL_LOG(L5, (_T("[File::RemoveFromMovesPendingReboot]")
				_T("[deleting PendingFileRenameOperations value]")));
			RET_IF_FAILED(RegKey::DeleteValue(kSessionManagerKey,
				kPendingFileRenameOps));
		} else  {
			// Let's write the modified value_multisz into the
			// PendingFileRenameOperations value
			UTIL_LOG(L5, (_T("[File::RemoveFromMovesPendingReboot]")
				_T("[rewriting multisz %d bytes]"),
				value_size_chars * sizeof(TCHAR)));
			RET_IF_FAILED(RegKey::SetValueMultiSZ(
				kSessionManagerKey,
				kPendingFileRenameOps,
				reinterpret_cast<byte*>(value_multisz.get()),
				value_size_chars * sizeof(TCHAR)));
		}
	}

	// Failure of GetPendingRenamesValueMinusDir() may indicate something
	// seriously wrong with the system. Propogate error.
	return hr;
}

// Did the user try to uninstall a previous install of the same version, and
// we couldn't clean up without a reboot?
// We check if there are any moves pending a reboot from the
// PendingFileRenameOperations in the registry.
// The prefix_match boolean controls whether we do an exact match on
// in_directory, or check all entries with the in_directory prefix.
bool File::AreMovesPendingReboot(const TCHAR* in_directory, bool prefix_match) {
	ASSERT1(in_directory && *in_directory);

	bool found = false;
	// scoped_array will free the value_multisz buffer on stack unwind:
	scoped_array<TCHAR> value_multisz;
	DWORD value_size_chars = 0;

	if (SUCCEEDED(GetPendingRenamesValueMinusDir(in_directory,
		prefix_match,
		address(value_multisz),
		&value_size_chars,
		&found)) && found) {
			return true;
	}

	return false;
}

HRESULT File::GetFileTime(const TCHAR* file_name,
						  FILETIME* created,
						  FILETIME* accessed,
						  FILETIME* modified) {
							  ASSERT1(file_name && *file_name);

							  bool is_dir = IsDirectory(file_name);
							  // To obtain a handle to a directory, call the CreateFile function with
							  // the FILE_FLAG_BACKUP_SEMANTICS flag
							  scoped_hfile file_handle(
								  ::CreateFile(file_name,
								  FILE_READ_DATA,
								  FILE_SHARE_READ,
								  NULL,
								  OPEN_EXISTING,
								  is_dir ? FILE_FLAG_BACKUP_SEMANTICS : NULL,
								  NULL));
							  HRESULT hr = S_OK;

							  if (!file_handle) {
								  hr = HRESULTFromLastError();
								  UTIL_LOG(LE, (_T("[File::GetFileTime]")
									  _T("[failed to open file][%s][0x%x]"), file_name, hr));
							  } else {
								  if (!::GetFileTime(get(file_handle), created, accessed, modified)) {
									  hr = HRESULTFromLastError();
									  UTIL_LOG(LEVEL_ERROR, (_T("[File::GetFileTime]")
										  _T("[failed to get file time][%s][0x%x]"),
										  file_name, hr));
								  }
							  }

							  return hr;
}

HRESULT File::SetFileTime(const TCHAR* file_name,
						  const FILETIME* created,
						  const FILETIME* accessed,
						  const FILETIME* modified) {
							  ASSERT1(file_name && *file_name);

							  bool is_dir = IsDirectory(file_name);
							  // To obtain a handle to a directory, call the CreateFile function with
							  // the FILE_FLAG_BACKUP_SEMANTICS flag
							  scoped_hfile file_handle(
								  ::CreateFile(file_name,
								  FILE_WRITE_ATTRIBUTES,
								  FILE_SHARE_WRITE,
								  NULL,
								  OPEN_EXISTING,
								  is_dir ? FILE_FLAG_BACKUP_SEMANTICS : NULL,
								  NULL));
							  HRESULT hr = S_OK;

							  if (!file_handle) {
								  hr = HRESULTFromLastError();
								  UTIL_LOG(LEVEL_ERROR, (_T("[File::GetFileTime]")
									  _T("[failed to open file][%s][0x%x]"),
									  file_name, hr));
							  } else {
								  BOOL res = ::SetFileTime(get(file_handle), created, accessed, modified);
								  if (!res) {
									  hr = HRESULTFromLastError();
									  UTIL_LOG(LEVEL_ERROR, (_T("[File::SetFileTime]")
										  _T("[failed to set file time][%s][0x%x]"),
										  file_name, hr));
								  }
							  }

							  return hr;
}

HRESULT File::Sync() {
	ASSERT1(handle_ != INVALID_HANDLE_VALUE);

	if (!::FlushFileBuffers(handle_)) {
		HRESULT hr = HRESULTFromLastError();
		UTIL_LOG(LEVEL_ERROR, (_T("[File::Sync]")
			_T("[FlushFileBuffers failed][%s][0x%x]"),
			file_name_, hr));
		return hr;
	}
	return S_OK;
}

HRESULT File::SeekToBegin() {
	return SeekFromBegin(0);
}

HRESULT File::SeekFromBegin(uint32 n) {
	ASSERT1(handle_ != INVALID_HANDLE_VALUE);

	if (::SetFilePointer(handle_, n, NULL, FILE_BEGIN) ==
		INVALID_SET_FILE_POINTER) {
			HRESULT hr = HRESULTFromLastError();
			UTIL_LOG(LEVEL_ERROR, (_T("[File::SeekFromBegin]")
				_T("[SetFilePointer failed][%s][0x%x]"),
				file_name_, hr));
			return hr;
	}
	pos_ = n;
	return S_OK;
}

// read nLen bytes starting at position n
// returns number of bytes read
//
// async operations:
//
// async_id - identifier of a sequence of async operations, 0 for synchronous
//
// if the async operation has not been initiated, we initiate it
// if it is in progress we do nothing
// if it has been completed we return the data
// does not delete async data entry
HRESULT File::ReadAt(const uint32 offset, byte* buf, const uint32 len,
					 const uint32, uint32* bytes_read) {
						 ASSERT1(handle_ != INVALID_HANDLE_VALUE);
						 ASSERT1(buf);
						 ASSERT1(len);  // reading 0 bytes is not valid (differs from CRT API)

						 RET_IF_FAILED(SeekFromBegin(offset));

						 DWORD read = 0;
						 if (!::ReadFile(handle_, buf, len, &read, NULL)) {
							 HRESULT hr = HRESULTFromLastError();
							 UTIL_LOG(LEVEL_ERROR, (_T("[File::ReadAt]")
								 _T("[ReadFile failed][%s][0x%x]"), file_name_, hr));
							 return hr;
						 }

						 if (bytes_read) {
							 *bytes_read = read;
						 }

						 return (read == len) ? S_OK : E_FAIL;
}


// reads up to max_len bytes from the start of the file
// not considered an error if there are less than max_len bytes read
// returns number of bytes read
HRESULT File::ReadFromStartOfFile(const uint32 max_len,
								  byte* buf,
								  uint32* bytes_read) {
									  ASSERT1(handle_ != INVALID_HANDLE_VALUE);
									  ASSERT1(buf);
									  ASSERT1(max_len);

									  RET_IF_FAILED(SeekFromBegin(0));

									  uint32 file_len = 0;
									  RET_IF_FAILED(GetLength(&file_len));

									  if (!file_len) {
										  if (bytes_read) {
											  *bytes_read = 0;
										  }
										  return S_OK;
									  }

									  uint32 len = max_len;
									  if (len > file_len) {
										  len = static_cast<uint32>(file_len);
									  }

									  return Read(len, buf, bytes_read);
}

// this function handles lines terminated with LF or CRLF
// all CR characters are removed from each line, and LF is assumed
// to be the end of line and is removed
HRESULT File::ReadLineAnsi(uint32 max_len, char* line, uint32* len) {
	ASSERT1(line);
	ASSERT1(max_len);

	char c = 0;
	uint32 len_read = 0;
	uint32 total_len = 0;

	while (SUCCEEDED(Read(1, reinterpret_cast<byte *>(&c), &len_read)) &&
		len_read  &&
		c != '\n') {
			if (total_len < max_len - 1 && c != '\r') {
				line[total_len++] = c;
			}
	}

	ASSERT1(total_len < max_len);
	line[total_len] = '\0';

	if (len) {
		*len = total_len;
	}

	return (len_read || total_len) ? S_OK : E_FAIL;
}

// used by ReadFromStartOfFile and ReadLineAnsi; not reading all requested bytes
// is not considered fatal
HRESULT File::Read(const uint32 len, byte* buf, uint32* bytes_read) {
	ASSERT1(handle_ != INVALID_HANDLE_VALUE);
	ASSERT1(buf);
	ASSERT1(len);

	DWORD read = 0;
	if (!::ReadFile(handle_, buf, len, &read, NULL)) {
		HRESULT hr = HRESULTFromLastError();
		UTIL_LOG(LEVEL_ERROR, (_T("[File::ReadAt]")
			_T("[ReadFile failed][%s][0x%x]"),
			file_name_, hr));
		return hr;
	}

	if (bytes_read) {
		*bytes_read = read;
	}

	return S_OK;
}


// returns number of bytes written
HRESULT File::WriteAt(const uint32 offset,
					  const byte* buf,
					  const uint32 len,
					  uint32,
					  uint32* bytes_written) {
						  ASSERT1(handle_ != INVALID_HANDLE_VALUE);
						  ASSERT1(!read_only_);
						  ASSERT1(buf);
						  ASSERT1(len);

						  RET_IF_FAILED(SeekFromBegin(offset));

						  return Write(buf, len, bytes_written);
}


// write buffer n times
HRESULT File::WriteN(const byte* buf,
					 const uint32 len,
					 const uint32 n,
					 uint32* bytes_written) {
						 ASSERT1(handle_ != INVALID_HANDLE_VALUE);
						 ASSERT1(!read_only_);
						 ASSERT1(buf);
						 ASSERT1(len);
						 ASSERT1(n);

						 HRESULT hr = S_OK;

						 uint32 total_wrote = 0;

						 byte* temp_buf = const_cast<byte*>(buf);

						 scoped_array<byte> encrypt_buf;

						 uint32 to_go = n;
						 while (to_go) {
							 uint32 wrote = 0;
							 hr = Write(temp_buf, len, &wrote);
							 if (FAILED(hr)) {
								 if (bytes_written) {
									 *bytes_written = total_wrote;
								 }
								 return hr;
							 }

							 total_wrote += wrote;
							 to_go--;
						 }

						 if (bytes_written) {
							 *bytes_written = total_wrote;
						 }
						 return hr;
}

// returns number of bytes written
HRESULT File::Write(const byte* buf, const uint32 len, uint32* bytes_written) {
	ASSERT1(handle_ != INVALID_HANDLE_VALUE);
	ASSERT1(!read_only_);
	ASSERT1(buf);
	ASSERT1(len);  // writing 0 bytes is not valid (differs from CRT API)

	byte* b = const_cast<byte*>(buf);

	scoped_array<byte> encrypt_buf;

	DWORD wrote = 0;
	if (!::WriteFile(handle_, b, len, &wrote, NULL)) {
		HRESULT hr = HRESULTFromLastError();
		UTIL_LOG(LEVEL_ERROR, (_T("[File::Write]")
			_T("[WriteFile failed][%s][0x%x]"),
			file_name_, hr));
		return hr;
	}

	if (bytes_written) {
		*bytes_written = wrote;
	}
	pos_ += wrote;

	return (wrote == len) ? S_OK : E_FAIL;
}

HRESULT File::ClearAt(const uint32 offset,
					  const uint32 len,
					  uint32* bytes_written) {
						  ASSERT1(handle_ != INVALID_HANDLE_VALUE);
						  ASSERT1(!read_only_);
						  ASSERT1(len);

						  byte zero[kZeroSize] = {0};
						  uint32 to_go = len;
						  uint32 written = 0;
						  uint32 pos = offset;

						  while (to_go) {
							  uint32 wrote = 0;
							  uint32 write_len = std::min(to_go, kZeroSize);
							  RET_IF_FAILED(WriteAt(pos, zero, write_len, 0, &wrote));

							  if (wrote != write_len) {
								  return E_FAIL;
							  }
							  pos += wrote;
							  written += wrote;
							  to_go -= write_len;
						  }

						  if (bytes_written) {
							  *bytes_written = written;
						  }
						  return S_OK;
}

// returns true on failure
// zeros new data if zero_data == true
HRESULT File::SetLength(const uint32 n, bool zero_data) {
	ASSERT1(handle_ != INVALID_HANDLE_VALUE);
	ASSERT1(!read_only_);
	ASSERT1(n <= kMaxFileSize);

	HRESULT hr = S_OK;

	uint32 len = 0;
	VERIFY1(SUCCEEDED(GetLength(&len)));

	if (len == n) {
		return S_OK;
	}

	// according to the documentation, the
	// new space will not be initialized
	if (n > len) {
		if (zero_data) {
			uint32 bytes_written = 0;
			RET_IF_FAILED(ClearAt(len, n - len, &bytes_written));
			if (bytes_written != n - len) {
				return E_FAIL;
			}
		} else {
			byte zero = 0;
			uint32 bytes_written = 0;
			RET_IF_FAILED(WriteAt(n - 1, &zero, 1, 0, &bytes_written));
			if (bytes_written != 1) {
				return E_FAIL;
			}
		}
	} else {
		SeekFromBegin(n);
		SetEndOfFile(handle_);
	}

	ASSERT1(SUCCEEDED(GetLength(&len)) && len == n);

	return S_OK;
}

HRESULT File::ExtendInBlocks(const uint32 block_size, uint32 size_needed,
							 uint32* new_size, bool clear_new_space) {
								 ASSERT1(new_size);

								 *new_size = size_needed;

								 if (*new_size % block_size) {
									 *new_size += block_size - (*new_size % block_size);
								 }

								 // is zero_data needed? may reduce fragmentation by causing the block to
								 // be written
								 return SetLength(*new_size, clear_new_space);
}

// returns S_OK on success
HRESULT File::GetLength(uint32* length) {
	ASSERT1(length);
	ASSERT1(handle_ != INVALID_HANDLE_VALUE);

	DWORD len = GetFileSize(handle_, NULL);
	if (len == INVALID_FILE_SIZE) {
		ASSERT(false, (_T("cannot get file length")));
		return E_FAIL;
	}
	*length = len;
	return S_OK;
}

HRESULT File::Touch() {
	ASSERT1(handle_ != INVALID_HANDLE_VALUE);

	FILETIME file_time;
	SetZero(file_time);

	::GetSystemTimeAsFileTime(&file_time);

	if (!::SetFileTime(handle_, NULL, NULL, &file_time)) {
		return HRESULTFromLastError();
	}
	return S_OK;
}

HRESULT File::Close() {
	ASSERT1(handle_ != INVALID_HANDLE_VALUE);

	HRESULT hr = S_OK;
	if (!::CloseHandle(handle_)) {
		hr = HRESULTFromLastError();
	}

	handle_ = INVALID_HANDLE_VALUE;

	return hr;
}

// this is just for consistency with other classes; does not do anything
HRESULT File::Reload(uint32* number_errors) {
	ASSERT1(number_errors);
	ASSERT1(handle_ != INVALID_HANDLE_VALUE);

	*number_errors = 0;
	return S_OK;
}

// this is just for consistency with other classes; does not do anything
HRESULT File::Verify(uint32* number_errors) {
	ASSERT1(number_errors);
	ASSERT1(handle_ != INVALID_HANDLE_VALUE);

	*number_errors = 0;
	return S_OK;
}

// this is just for consistency with other classes; does not do anything
HRESULT File::Dump() {
	ASSERT1(handle_ != INVALID_HANDLE_VALUE);
	return S_OK;
}

// for consistency with other classes
HRESULT File::GetSizeOnDisk(uint64* size_on_disk) {
	ASSERT1(size_on_disk);
	ASSERT1(handle_ != INVALID_HANDLE_VALUE);

	uint32 len = 0;
	RET_IF_FAILED(GetLength(&len));

	*size_on_disk = len;
	return S_OK;
}

// for consistency with other classes
HRESULT File::GetReloadDiskSpaceNeeded(uint64* bytes_needed) {
	ASSERT1(bytes_needed);
	ASSERT1(handle_ != INVALID_HANDLE_VALUE);

	uint32 len = 0;
	RET_IF_FAILED(GetLength(&len));

	*bytes_needed = len;
	return S_OK;
}

// Get the file size
HRESULT File::GetFileSizeUnopen(const TCHAR* filename, uint32* out_size) {
	ASSERT1(filename);
	ASSERT1(out_size);

	WIN32_FILE_ATTRIBUTE_DATA data;
	SetZero(data);

	if (!::GetFileAttributesEx(filename, ::GetFileExInfoStandard, &data)) {
		return HRESULTFromLastError();
	}

	*out_size = data.nFileSizeLow;

	return S_OK;
}

// Get the last time with a file was written to, and the size
HRESULT File::GetLastWriteTimeAndSize(const TCHAR* file_path,
									  SYSTEMTIME* out_time,
									  unsigned int* out_size) {
										  ASSERT1(file_path);

										  WIN32_FIND_DATA wfd;
										  SetZero(wfd);

										  HANDLE find = ::FindFirstFile(file_path, &wfd);
										  if (find == INVALID_HANDLE_VALUE) {
											  return HRESULTFromLastError();
										  }

										  ::FindClose(find);

										  if (out_size) {
											  *out_size = wfd.nFileSizeLow;
										  }

										  if (out_time) {
											  // If created time is newer than write time, then use that instead
											  // [it tends to be more relevant when copying files around]
											  FILETIME* latest_time = NULL;
											  if (::CompareFileTime(&wfd.ftCreationTime, &wfd.ftLastWriteTime) > 0) {
												  latest_time = &wfd.ftCreationTime;
											  } else {
												  latest_time = &wfd.ftLastWriteTime;
											  }

											  if (!::FileTimeToSystemTime(latest_time, out_time)) {
												  return HRESULTFromLastError();
											  }
										  }

										  return S_OK;
}

bool File::AreFilesIdentical(const TCHAR* filename1, const TCHAR* filename2) {
	UTIL_LOG(L4, (_T("[File::AreFilesIdentical][%s][%s]"), filename1, filename2));

	uint32 file_size1 = 0;
	HRESULT hr = File::GetFileSizeUnopen(filename1, &file_size1);
	if (FAILED(hr)) {
		UTIL_LOG(LE, (_T("[GetFileSizeUnopen failed file_size1][0x%x]"), hr));
		return false;
	}

	uint32 file_size2 = 0;
	hr = File::GetFileSizeUnopen(filename2, &file_size2);
	if (FAILED(hr)) {
		UTIL_LOG(LE, (_T("[GetFileSizeUnopen failed file_size2][0x%x]"), hr));
		return false;
	}

	if (file_size1 != file_size2) {
		UTIL_LOG(L3, (_T("[file_size1 != file_size2][%d][%d]"),
			file_size1, file_size2));
		return false;
	}

	File file1;
	hr = file1.OpenShareMode(filename1, false, false, FILE_SHARE_READ);
	if (FAILED(hr)) {
		UTIL_LOG(LE, (_T("[file1.OpenShareMode failed][0x%x]"), hr));
		return false;
	}

	File file2;
	hr = file2.OpenShareMode(filename2, false, false, FILE_SHARE_READ);
	if (FAILED(hr)) {
		UTIL_LOG(LE, (_T("[file2.OpenShareMode failed][0x%x]"), hr));
		return false;
	}

	static const uint32 kBufferSize = 0x10000;
	std::vector<uint8> buffer1(kBufferSize);
	std::vector<uint8> buffer2(kBufferSize);
	uint32 bytes_left = file_size1;

	while (bytes_left > 0) {
		uint32 bytes_to_read = std::min(bytes_left, kBufferSize);
		uint32 bytes_read1 = 0;
		uint32 bytes_read2 = 0;

		hr = file1.Read(bytes_to_read, &buffer1.front(), &bytes_read1);
		if (FAILED(hr)) {
			UTIL_LOG(LE, (_T("[file1.Read failed][%d][%d][0x%x]"),
				bytes_left, bytes_to_read, hr));
			return false;
		}

		hr = file2.Read(bytes_to_read, &buffer2.front(), &bytes_read2);
		if (FAILED(hr)) {
			UTIL_LOG(LE, (_T("[file2.Read failed][%d][%d][0x%x]"),
				bytes_left, bytes_to_read, hr));
			return false;
		}

		if (bytes_to_read != bytes_read1 || bytes_to_read != bytes_read2) {
			UTIL_LOG(LE,
				(_T("[bytes_to_read != bytes_read1 || bytes_to_read != bytes_read2]")
				_T("[%d][%d][%d]"), bytes_to_read, bytes_read1, bytes_read2));
			return false;
		}

		if (memcmp(&buffer1.front(), &buffer2.front(), bytes_read1) != 0) {
			UTIL_LOG(L3, (_T("[memcmp failed][%d][%d]"), bytes_left, bytes_read1));
			return false;
		}

		if (bytes_left < bytes_to_read) {
			UTIL_LOG(LE, (_T("[bytes_left < bytes_to_read][%d][%d]"),
				bytes_left, bytes_to_read));
			return false;
		}

		bytes_left -= bytes_to_read;
	}

	return true;
}

FileLock::FileLock() {
}

FileLock::~FileLock() {
	Unlock();
}

HRESULT FileLock::Lock(const TCHAR* file) {
	std::vector<CString> files;
	files.push_back(file);
	return Lock(files);
}

HRESULT FileLock::Lock(const std::vector<CString>& files) {
	ASSERT1(!files.empty());

	// Try to lock all files
	size_t curr_size = handles_.size();
	for (size_t i = 0; i < files.size(); ++i) {
		scoped_hfile handle(::CreateFile(files[i],
			GENERIC_READ,
			FILE_SHARE_READ,
			NULL,
			OPEN_EXISTING,
			FILE_ATTRIBUTE_NORMAL,
			NULL));
		if (!handle) {
			UTIL_LOG(LEVEL_ERROR,
				(_T("[FileLock::Lock - failed to lock file][%s][0x%x]"),
				files[i], HRESULTFromLastError()));
			break;
		}
		handles_.push_back(release(handle));
	}

	// Cleanup if we fail to lock all the files
	if (curr_size +  files.size() < handles_.size()) {
		for (size_t i = handles_.size() - 1; i >= curr_size; --i) {
			VERIFY(::CloseHandle(handles_[i]), (_T("")));
			handles_.pop_back();
		}
		return E_FAIL;
	}

	return S_OK;
}

HRESULT FileLock::Unlock() {
	for (size_t i = 0; i < handles_.size(); ++i) {
		VERIFY(::CloseHandle(handles_[i]), (_T("")));
	}
	handles_.clear();
	return S_OK;
}


// path_name: the directory to watch
// watch_subtree: watch all subdirectory changes  or
//                only immediate child values
// notify_filter: See the documentation for FindFirstChangeNotification
FileWatcher::FileWatcher(const TCHAR* path_name, bool watch_subtree,
						 DWORD notify_filter)
						 : path_name_(path_name),
						 watch_subtree_(watch_subtree),
						 notify_filter_(notify_filter) {
							 ASSERT1(path_name && *path_name);
							 UTIL_LOG(L3, (_T("[FileWatcher::FileWatcher][%s]"), path_name));
}

// Get the event that is signaled on store changes.
HANDLE FileWatcher::change_event() const {
	ASSERT(valid(change_event_), (_T("call FileWatcher::SetupEvent first")));
	return get(change_event_);
}


// Called to create/reset the event that gets signaled
// any time the store changes.  Access the created
// event using change_event().
HRESULT FileWatcher::EnsureEventSetup() {
	UTIL_LOG(L3, (_T("[FileWatcher::EnsureEventSetup]")));
	if (!valid(change_event_)) {
		reset(change_event_, ::FindFirstChangeNotification(path_name_,
			watch_subtree_,
			notify_filter_));
		if (!valid(change_event_)) {
			ASSERT(false, (_T("unable to get file change notification")));
			return E_FAIL;
		}
		// path name was only needed to set-up the event and now that is done....
		path_name_.Empty();
		return S_OK;
	}

	// if the event is set-up and no changes have occurred,
	// then there is no need to re-setup the event.
	if (valid(change_event_) && !HasChangeOccurred()) {
		return NOERROR;
	}

	return ::FindNextChangeNotification(get(change_event_)) ? S_OK : E_FAIL;
}